package progma.lang.compiler;

import static org.objectweb.asm.Opcodes.*;

import java.util.HashMap;
import java.util.Stack;

import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;

import progma.lang.PFunction;

import com.sun.xml.internal.ws.org.objectweb.asm.Type;

public class BytecodeGenerator {

	private Namespace cns;
	private Stack<Namespace> nss = new Stack<Namespace>();
	private HashMap<String, byte[]> classes = new HashMap<String, byte[]>();
	private String ns;
	private MethodVisitor cinit;

	public BytecodeGenerator() {
		cns = new Namespace(new ClassWriter(ClassWriter.COMPUTE_FRAMES
				+ ClassWriter.COMPUTE_MAXS), null);
		nss.push(cns);
	}

	public void start(String className, int access, String superClass,
			String[] ifaces) {
		cns.name = className;
		setNamespace(className);
		cns.cw.visit(V1_5, access, className, null, superClass, ifaces);
		cinit = cns.cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
	}

	public void end() {
		cinit.visitEnd();
		cns.cw.visitEnd();
		classes.put(cns.name, cns.cw.toByteArray());
	}

	public void newVariable(String name, int access, String type) {
		cns.cw.visitField(access, name, type, null, null).visitEnd();
	}

	// ////////////////// Functions //////////////////
	public void startFunction(String name, int access, String signature) {
		String fname = name + "$function";
		newVariable(fname, access, Type.getDescriptor(PFunction.class));
		cns = new Namespace(new ClassWriter(ClassWriter.COMPUTE_FRAMES
				+ ClassWriter.COMPUTE_MAXS), fullname(fname));
		cns.cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, fname, null,
				"progma/lang/PFunction", null);
		nss.push(cns);
	}

	public void endFunction() {
		cns.cw.visitEnd();
		classes.put(cns.name, cns.cw.toByteArray());
		nss.pop();
		cns = nss.peek();
	}

	// ////////////////// end Functions //////////////////

	// ////////////////// Class //////////////////
	public void newClass(String name, int access, String superClass,
			String[] ifaces) {
		cns = new Namespace(new ClassWriter(ClassWriter.COMPUTE_FRAMES
				+ ClassWriter.COMPUTE_MAXS), fullname(name));
		cns.cw.visit(V1_5, access, fullname(name), null, superClass, ifaces);
		nss.push(cns);
	}

	public void endClass() {
		cns.cw.visitEnd();
		classes.put(cns.name, cns.cw.toByteArray());
		nss.pop();
		cns = nss.peek();
	}

	public void newClassField(String name, int access, String type) {
		cns.cw.visitField(access, name, type, null, null).visitEnd();
	}

	// ////////////////// end Class //////////////////

	public HashMap<String, byte[]> getClasses() {
		return classes;
	}

	private String fullname(String name) {
		if (ns != null && !ns.isEmpty()) {
			return ns + "/" + name;
		}
		return name;
	}

	private void setNamespace(String name) {
		if (name.contains("/")) {
			int i = name.lastIndexOf("/");
			ns = name.substring(0, i);
		}
	}

	private static class Namespace {
		ClassWriter cw;
		String name;

		public Namespace(ClassWriter cw, String name) {
			this.name = name;
			this.cw = cw;
		}
	}
}
